﻿using System;
using System.Net.Sockets;
using System.Net;
using System.Windows;
using System.Text;
using System.Windows.Controls;

namespace SBPweb.Talk.Client.Library
{

    public class SocketClient
    {

        #region Private fields

        private const string host = "212.40.96.99";
        private const int port = 443;

        private Socket socket;
        private EndPoint endPoint;

        private bool connected = false;

        private UIElement dispatcherObject = new TextBlock();

        #endregion

        #region Constructor logic

        public SocketClient()
        {

        }

        #endregion

        #region Events

        public event EventHandler<MessageEventArgs> MessageReceived;

        #endregion

        #region Methods

        #region Public

        public void Connect()
        {
            DnsEndPoint endPoint = new DnsEndPoint(host, port);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            SocketAsyncEventArgs context = new SocketAsyncEventArgs();

            context.UserToken = socket;
            context.RemoteEndPoint = endPoint;

            context.Completed += new EventHandler<SocketAsyncEventArgs>(OnAsyncSocketOperationComplete);

            if (!socket.ConnectAsync(context))
            {
                // this is the dumb part of sockets programming.
                // if ConnectAsync returns false, the event isn't
                // fired and you instead need to do your checking
                // here. IMHO, it should fire the event regardless

                HandleConnect(context);
            }
        }
        
        public void Disconnect()
        {
            connected = false;
            socket.Close();
        }

        public void SendMessage(string message)
        {
            if (connected)
            {
                byte[] bytes = Encoding.UTF8.GetBytes(message);

                SocketAsyncEventArgs context = new SocketAsyncEventArgs();
                context.SetBuffer(bytes, 0, bytes.Length);
                context.UserToken = socket;
                context.RemoteEndPoint = endPoint;
                context.Completed += new EventHandler<SocketAsyncEventArgs>(OnAsyncSocketOperationComplete);

                socket.SendAsync(context);
            }
            else
            {
                throw new SocketException((int)SocketError.NotConnected);
            }
        }

        #endregion

        #region Private helpers

        // Handles the connect event, and starts receiving
        private void HandleConnect(SocketAsyncEventArgs context)
        {
            if (context.SocketError != SocketError.Success)
            {
                throw new SocketException((int)context.SocketError);
            }
            else
            {
                connected = true;

                // we're connected, so start receiving
                StartReceiving();
            }
        }

        // Async receive
        private void StartReceiving()
        {
            // hey, at least it's longer than twitter :)
            byte[] buffer = new byte[255];

            SocketAsyncEventArgs context = new SocketAsyncEventArgs();
            context.SetBuffer(buffer, 0, buffer.Length);
            context.UserToken = socket;
            context.RemoteEndPoint = endPoint;
            context.Completed += new EventHandler<SocketAsyncEventArgs>(OnAsyncSocketOperationComplete);

            if (!socket.ReceiveAsync(context))
            {
                // same sync/async hackery as Connect.
                // Typically there wouldn't be a message here. If it
                // was the normal case that a message is always waiting,
                // this code would end up with a stackoverflow
                HandleReceive(context);
            }
        }

        // Process a newly received message
        private void HandleReceive(SocketAsyncEventArgs context)
        {
            string message = Encoding.UTF8.GetString(context.Buffer, context.Offset, context.BytesTransferred);

            // raise the event on the UI thread
            dispatcherObject.Dispatcher.BeginInvoke(
                delegate
                {
                    if (MessageReceived != null)
                        MessageReceived(this, new MessageEventArgs(message));
                });

            // set up to receive next message
            StartReceiving();
        }

        #endregion

        #endregion

        #region Event handlers

        private void OnAsyncSocketOperationComplete(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    HandleConnect(e);
                    break;

                case SocketAsyncOperation.Receive:
                    HandleReceive(e);
                    break;

                case SocketAsyncOperation.Send:
                    break;
            }
        }

        #endregion

    }

}
